#pragma once

#include <HybridRenderer\Render\VAO.h>
#include <HybridRenderer\Render\VBO.h>
#include <HybridRenderer\Render\IBO.h>

#include <HybridRenderer\Scene\SceneNode.h>

#include <PolyVoxCore/MaterialDensityPair.h>
#include <PolyVoxCore/SimpleVolume.h>
#include <PolyVoxCore/LowPassFilter.h>
#include <PolyVoxCore/RawVolume.h>
#include <PolyVoxCore/SurfaceMesh.h>
#include <PolyVoxImpl/Utility.h>

#include <libtcod\libtcod.hpp>

#include <Eigen/Eigen>
#include <memory>
#include <map>

namespace Scene
{
	class Volume;
	typedef std::shared_ptr<Volume> VolumePtr;

	class Volume : public SceneNode
	{
	public:
		Volume();

		void render(const Render::ShaderPtr &active_program) override;

	private:
		void createSphereInVolume(float fRadius, uint8_t uValue);
		void createTerrainInVolume(uint8_t uValue);

		struct OpenGLSurfaceMesh
		{
			Render::VAOPtr vao;
			Render::VBOPtr vbo;
			Render::IBOPtr ibo;
			PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> sourceMesh;

		};
		struct OpenGLColour
		{
			GLfloat red;
			GLfloat green;
			GLfloat blue;
		};

		void BuildOpenGLSurfaceMesh();
		void renderVertexBufferObject(const Render::ShaderPtr &active_program, unsigned int uLodLevel);
		OpenGLColour convertMaterialIDToColour(uint8_t materialID);

		PolyVox::SimpleVolume<PolyVox::MaterialDensityPair44> volData;
		
		std::unique_ptr<OpenGLSurfaceMesh> mesh;
		PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> *surface_mesh;

		float max_height;
		TCODHeightMap heightmap;

		
		// This function wraps the given number so that it remains within its 
		// base. Returns a number between 0 and base - 1.
		// For example if the base given was 10 and the parameter was 10 it
		// would wrap it so that the number is now a 0. If the number given
		// were -1, then the result would be 9. This function can also be
		// used everywhere where a number needs to be kept within a certain
		// range, for example angles (0 and 360) and radians (0 to TWO_PI).
		int CalcBase(const int base, int num)
		{
			if(num >= 0 && num < base)
				return num;    // No adjustment neccessary
			if(num < 0) 
			{
				num %= base;
				num += base;
			} else {
				num %= base;
			}
			return num;
		}
		// Same as CalcBase() above, except using floats
		float CalcBaseFloat(const float base, float num)
		{
			if(num >= 0.0f && num < base)
				return num;    // No adjustment neccessary
			if(num < 0.0f) 
				return fmodf(num, base) + base;
			return fmodf(num, base);
		}
	};
}
